// 游戏房间管理模块
#ifndef __ROOM__HPP
#define __ROOM__HPP

#include "util.hpp"
#include "Log.hpp"
#include "db.hpp"
#include "online_user.hpp"

/*采用 Restful 风格通信*/

#define BOARD_ROWS 15 /* 15*15 棋盘 */
#define BOARD_COLS 15 /* 15*15 棋盘 */

#define WHITE_CHESS 1 /*白棋*/
#define BLACK_CHESS 2 /*黑棋*/

typedef enum
{
    OCCUPYING, /*房间占用中*/
    IDLING     /*房间空闲中*/
} status;

/*房间类*/
class Room
{
private:
    /*判断当前棋子是否 5 星连珠 , 传过来一个方向 , 里面会自动判断另一个方向*/
    bool FiveStar(int row, int col, int row_off /*行偏移量*/, int col_off /*列偏移量*/, int color)
    {
        /*更新新的行列*/
        int new_row = row + row_off;
        int new_col = col + col_off;
        int count = 1; /*同色棋子数量*/
        /*判断传来的方向*/
        while (new_row >= 0 && new_row < BOARD_ROWS && new_col >= 0 && new_col < BOARD_COLS && _board[new_row][new_col] == color)
        {
            /*棋子同色 ++ 数量*/
            count++;
            if (count >= 5)
                return true; /*避免无效查询*/
            new_row += row_off;
            new_col += col_off;
        }
        /*判断传来的反方向*/
        new_row = row - row_off;
        new_col = col - col_off;
        while (new_row >= 0 && new_row < BOARD_ROWS && new_col >= 0 && new_col < BOARD_COLS && _board[new_row][new_col] == color)
        {
            /*棋子同色 ++ 数量*/
            count++;
            if (count >= 5)
                return true;
            new_row -= row_off;
            new_col -= col_off;
        }

        /*累加两边数量 */
        return (count >= 5);
    }

    /*返回胜利者ID , 判断棋玩家是否胜利*/
    uint64_t check_win(int row, int col, int color)
    {
        /*四方向有一个连星就胜利*/
        if (FiveStar(row, col, 0, 1, color)       /*横方向 - 右边*/
            || FiveStar(row, col, -1, 0, color)   /*列方向 - 上边*/
            || FiveStar(row, col, -1, 1, color)   /*正斜方向 - 右上*/
            || FiveStar(row, col, -1, -1, color)) /*反斜方向 - 左上*/
        {
            return color == WHITE_CHESS ? _white_player_id : _black_player_id;
        }

        return 0;
    }

    /*设置敏感信息*/
    void InitSensitiveInfo()
    {
        _sensitive_info.insert(std::make_pair(1, "傻逼"));
        _sensitive_info.insert(std::make_pair(2, "菜鸡"));
        _sensitive_info.insert(std::make_pair(3, "废物"));
        //.... 
    }

    std::string GetSensitiveInfo(const int key)
    {
        auto str = _sensitive_info.find(key);
        if (str == _sensitive_info.end())
        {
            return std::string();
        }
        return str->second;
    }

public:
    Room(uint64_t room_id, user_table *tb_user, OnlineUserManage *online_user)
        : _room_id(room_id),
          _room_status(OCCUPYING),
          _player_num(0),
          _user_table(tb_user),
          _online_user(online_user),
          _board(BOARD_ROWS, std::vector<int>(BOARD_COLS, 0)), /* 15*15 二维数组初始化 , 每一个元素均初始化为 0 */
          _sensitive_info()                                    /*空的map*/
    {
        DBGLOG("room create success ! ");
        InitSensitiveInfo();
    }
    ~Room()
    {
        DBGLOG("room destory success ! ");
    }

    /*统一处理所有请求 , 根据请求派发动作*/
    void HandleRequest(Json::Value &req)
    {
        /*回复的 Json */
        Json::Value json_resp = req;
        /*1. 判断房间号是否匹配*/
        uint64_t room_id = req["room_id"].asInt64();
        if (room_id != _room_id)
        {
            json_resp["result"] = false;
            json_resp["reason"] = "房间号不匹配 !";
            /*广播给其他玩家*/
            return Broadcast(json_resp);
        }
        /*2. 判断请求类型 , 派发不同动作*/
        if (req["optype"].asString() == "put_chess")
        {
            /*下棋动作*/
            json_resp = PlayChess(req);
            /*执行数据库*/
            uint64_t winner = json_resp["winner"].asInt64();
            uint64_t loser = winner == _white_player_id ? _black_player_id : _white_player_id;
            /*已经有胜负 , 更新数据库数据并设置房间状态为空闲*/
            if (winner != 0)
            {
                _user_table->Win(winner);
                _user_table->Lose(loser);
                _room_status = IDLING;
            }
        }
        else if (req["optype"].asString() == "chat")
        {
            /*聊天动作*/
            json_resp = Chat(req);
        }
        else
        {
            json_resp["optype"] = req["optype"].asString();
            json_resp["result"] = false;
            json_resp["reason"] = "未知请求类型";
        }

        /*DEBUG 测试*/
        std::string body;
        json_util::Serialize(json_resp, body);
        DBGLOG("body : %s ", body.c_str());

        /*3. 广播消息*/
        return Broadcast(json_resp);
    }

    // 提供两种动作
    /*1. 下棋动作 , 完成需要返回请求信息*/
    Json::Value PlayChess(Json::Value &req)
    {
        /*回复的 Json */
        Json::Value json_resp = req;
        /*1. 判断房间号是否匹配 -- 外部统一处理请求时判断了*/
        /*2. 判断是两个玩家是否都在线 , 有一方退出了另一方胜利*/

        if (_online_user->IsInGameRoom(_white_player_id) == false)
        {
            /*黑棋玩家胜利*/
            json_resp["result"] = true;
            json_resp["reason"] = "对方掉线 , 比赛结束 , 恭喜你赢得本对局 !";
            json_resp["winner"] = _black_player_id; /*0表示为分胜负 , 非0表示谁胜利 !*/
            // 返回信息方便外部广播使用
            return json_resp;
        }
        if (_online_user->IsInGameRoom(_black_player_id) == false)
        {
            /*白棋玩家胜利*/
            json_resp["result"] = true;
            json_resp["reason"] = "对方掉线 , 比赛结束 , 恭喜你赢得本对局 !";
            json_resp["winner"] = _white_player_id; /*0表示为分胜负 , 非0表示谁胜利 !*/
            // 返回信息方便外部广播使用
            return json_resp;
        }

        /*3. 判断走棋是否合法*/
        int row = req["row"].asInt(); /*请求的走棋行*/
        int col = req["col"].asInt(); /*请求的走棋列*/

        if (row < 0 || row >= BOARD_ROWS || col < 0 || col >= BOARD_COLS)
        {
            json_resp["result"] = false;
            json_resp["reason"] = "请在棋盘内下棋 !";
            return json_resp;
        }

        if (_board[row][col] != 0)
        {
            /*该位置不合法 , 错误请求结果*/
            json_resp["result"] = false;
            json_resp["reason"] = "当前位置已有棋子 , 请重新选择新的位置 !";
            // 返回信息方便外部广播使用
            return json_resp;
        }
        /*4. 判断是否有玩家胜利 , 正常对战*/
        int cur_uid = req["uid"].asInt();
        int cur_color = cur_uid == _white_player_id ? WHITE_CHESS : BLACK_CHESS;
        _board[row][col] = cur_color; /*置棋*/
        uint64_t win_id = check_win(row, col, cur_color);
        if (win_id != 0)
        {
            json_resp["reason"] = "五星连珠 , 很牛逼 , 你赢了 !";
            json_resp["winner"] = (Json::UInt64)win_id;
        }
        else
            json_resp["reason"] = "很遗憾 , 当前平局 !";

        json_resp["result"] = true;
        return json_resp;
    }
    /*2. 聊天动作 */
    Json::Value Chat(Json::Value &req)
    {
        Json::Value json_resp = req;
        std::string message = json_resp["message"].asString();
        /*检测消息中是否包含敏感词*/
        if (!message.empty())
        {
            for (const auto &pair : _sensitive_info)
            {
                const std::string &sensitive = pair.second;
                if (!sensitive.empty() && message.find(sensitive) != std::string::npos)
                {
                    json_resp["result"] = false;
                    json_resp["reason"] = "消息中包含敏感词，不能发送！";
                    return json_resp;
                }
            }
        }
        /*返回方便后续广播*/
        json_resp["result"] = true;
        return json_resp;
    }

    /*广播*/
    void Broadcast(Json::Value &msg)
    {
        /*1 . 序列化*/
        std::string body;
        if (json_util::Serialize(msg, body) == false)
        {
            DBGLOG("Serialize [ Json::Value &msg ] failed !");
        }

        /*2 . 通过用户ID找到连接 , 广播*/
        wsserver_t::connection_ptr white_conn = _online_user->GetConnectionFromRoom(_white_player_id);
        if (white_conn.get() == nullptr)
        {
            DBGLOG("find white chess user [ %lu ] connection failed , broadcast failed !", _white_player_id);
        }
        else
        {
            /*广播*/
            white_conn->send(body);
        }

        wsserver_t::connection_ptr black_conn = _online_user->GetConnectionFromRoom(_black_player_id);
        if (black_conn.get() == nullptr)
        {
            DBGLOG("find black chess user [ %lu ] connection failed , broadcast failed !", _black_player_id);
        }
        else
        {
            /*广播*/
            black_conn->send(body);
        }

        return;
    }

    /*玩家可能中途退出, 退出一方失败 , 需要封装独立接口访问数据库 , 该接口不属于请求*/
    void HandlingPlayerExit(const uint64_t uid)
    {
        Json::Value json_resp;
        if (_room_status == OCCUPYING)
        {
            /*一方退出 , 另一方胜利*/
            uint64_t winner = uid == _white_player_id ? _black_player_id : _white_player_id;
            uint64_t loser = winner == _white_player_id ? _black_player_id : _white_player_id;
            /*构造完整 json 回复信息*/
            json_resp["optype"] = "put_chess";
            json_resp["uid"] = uid;
            json_resp["room_id"] = _room_id;
            json_resp["row"] = -1; /*无效位置*/
            json_resp["col"] = -1; /*无效位置*/
            json_resp["result"] = true;
            json_resp["reason"] = "对方掉线 , 比赛结束 , 恭喜你赢得本对局 !";
            json_resp["winner"] = winner;
            /*更新数据库数据*/
            _user_table->Win(winner);
            _user_table->Lose(loser);
            _room_status = IDLING;
            /*广播给其它用户*/
            Broadcast(json_resp);
        }

        /*玩家退出 , 数量 -- */
        _player_num--;
        return;
    }

    /*设置白棋玩家ID*/
    void SetWhiteID(const uint64_t uid)
    {
        _white_player_id = uid;
        _player_num++;
    }
    /*设置黑棋玩家ID*/
    void SetBlackID(const uint64_t uid)
    {
        _black_player_id = uid;
        _player_num++;
    }
    /*获取白棋玩家ID*/
    uint64_t GetWhiteID() { return _white_player_id; }
    /*获取黑棋玩家ID*/
    uint64_t GetBlackID() { return _black_player_id; }

    /*获取房间ID*/
    uint64_t GetRoomID() { return _room_id; }
    /*获取房间玩家数量*/
    int GetRoomPlayerNum() { return _player_num; }
    /*获取房间状态*/
    status GetRoomStatus() { return _room_status; }

private:
    uint64_t _room_id;                                    /*房间ID*/
    status _room_status;                                  /*房间状态*/
    int _player_num;                                      /*房间中玩家数量 , int足够了*/
    uint64_t _white_player_id;                            /*白棋玩家ID*/
    uint64_t _black_player_id;                            /*黑棋玩家ID*/
    user_table *_user_table;                              /*用户信息表句柄 , 操作数据库*/
    OnlineUserManage *_online_user;                       /*在线用户句柄 , 广播消息*/
    std::vector<std::vector<int>> _board;                 /*棋盘 -- 二维数组*/
    std::unordered_map<int, std::string> _sensitive_info; /*敏感信息*/
};

/*因为要传参 , 所以用 shared ptr */
typedef std::shared_ptr<Room> room_ptr;

/*房间管理类 , 管理每一个房间类*/
class RoomManage
{
public:
    RoomManage(user_table *user_table, OnlineUserManage *onlie_user)
        : _room_id_count(1),
          _user_table(user_table),
          _online_user(onlie_user)
    {
        DBGLOG("房间管理初始化成功 !");
    }
    ~RoomManage()
    {
        DBGLOG("房间管理销毁成功 !");
    }

    /*为两个匹配成功的用户创建 , 创建成功返回房间指针*/
    room_ptr CreateRoom(uint64_t uid1, uint64_t uid2)
    {
        /*1. 因为是在大厅匹配成功后会进入这里 , 所以要判断二者是否都在大厅 , 如果有掉线的 , 就不创建房间了*/
        // 两个用户在游戏大厅中进行对战匹配，匹配成功后创建房间
        // 1. 校验两个用户是否都还在游戏大厅中，只有都在才需要创建房间。
        if (_online_user->IsInGameHall(uid1) == false)
        {
            DBGLOG("用户：%lu 不在大厅中，创建房间失败!", uid1);
            return room_ptr();
        }
        if (_online_user->IsInGameHall(uid2) == false)
        {
            DBGLOG("用户：%lu 不在大厅中，创建房间失败!", uid2);
            return room_ptr();
        }
        // 2. 两个用户在线 , 创建房间，将用户信息添加到房间中

        std::unique_lock<std::mutex> lock(_mutex);
        /*创建房间 , new room 对象*/
        room_ptr rp(new Room(_room_id_count, _user_table, _online_user));
        rp->SetWhiteID(uid1);
        rp->SetBlackID(uid2);
        // 3. 将房间信息管理起来
        _rid_room.insert(std::make_pair(_room_id_count, rp));
        _uid_rid.insert(std::make_pair(uid1, _room_id_count));
        _uid_rid.insert(std::make_pair(uid2, _room_id_count));
        _room_id_count++;
        // 4. 返回房间信息
        return rp;
    }

    bool DestoryRoom(uint64_t rid)
    {
        /*1. 通过房间ID查找指定房间信息*/
        room_ptr room = GetRoomByRoomId(rid);
        if (room.get() == nullptr)
        {
            DBGLOG("get %lu room_ptr failed !", rid);
            return false;
        }
        /*2. 通过房间查找指定用户ID*/
        uint64_t white_id = room->GetWhiteID();
        uint64_t black_id = room->GetBlackID();

        /*保证线程安全*/
        {
            std::unique_lock<std::mutex> lock(_mutex);
            /*3. 移除房间管理中的用户信息*/
            _uid_rid.erase(white_id);
            _uid_rid.erase(black_id);
            /*4. 移除房间*/
            _rid_room.erase(rid);
        }
        return true;
    }

    /*房间中有用户 , 可以通过用户ID 获取房间信息*/
    room_ptr GetRoomByUserId(uint64_t uid)
    {
        std::unique_lock<std::mutex> lock(_mutex);
        /*1 . 通过用户ID找到房间ID*/
        auto uit = _uid_rid.find(uid);
        if (uit == _uid_rid.end())
        {
            ERRLOG("by user_id find room_id failed !");
            return room_ptr();
        }
        uint64_t room_id = uit->second;
        /*2 . 通过房间ID找房间信息*/
        auto rit = _rid_room.find(room_id);
        if (rit == _rid_room.end())
        {
            ERRLOG("by room_id find room_ptr failed !");
            return room_ptr();
        }
        return rit->second;
    }
    /*每一个房间都有独立的ID , 通过房间ID 获取房间信息*/
    room_ptr GetRoomByRoomId(uint64_t room_id)
    {
        std::unique_lock<std::mutex> lock(_mutex);
        /*通过房间ID找房间信息*/
        auto rit = _rid_room.find(room_id);
        if (rit == _rid_room.end())
        {
            ERRLOG("by room_id find room_ptr failed !");
            return room_ptr();
        }
        return rit->second;
    }

    /*移除用户 , 如果房间中没有用户了 , 就销毁房间 !*/
    bool RemoveRoomUser(uint64_t uid)
    {
        /*1. 获取房间*/
        room_ptr room = GetRoomByUserId(uid);
        if (room.get() == nullptr)
        {
            ERRLOG("get %lu room_ptr failed !", uid);
            return false;
        }

        /*2. 处理房间中玩家退出动作 , 会--玩家数量*/
        room->HandlingPlayerExit(uid);
        /*3. 玩家为0 , 销毁房间*/
        if (room->GetRoomPlayerNum() == 0)
        {
            DestoryRoom(room->GetRoomID());
        }
        return true;
    }

private:
    uint64_t _room_id_count; /*用一个计数器分配 , 默认从1 开始*/
    user_table *_user_table;
    OnlineUserManage *_online_user;
    std::mutex _mutex;                                /*保护线程安全*/
    std::unordered_map<uint64_t, room_ptr> _rid_room; /*建立房间ID和房间智能指针的映射 , 方便快速查找*/
    std::unordered_map<uint64_t, uint64_t> _uid_rid;  /*建立用户ID和房间ID的映射 , 这样可以通过用户ID找到房间ID进而找到房间信息*/
};

#endif

/**
 * 本代码实现的是 : 房间管理模块 , 代码还未经过标准测试 , 只是简单的语法检查 , 具体需结合业务测试
 *  具体描述总结 - 飞书 - 更多细节
 */